﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Director.DomainModels;
using Director.Indicators;

namespace Director
{
	class Charter
	{
		static readonly Typeface _typeface = new Typeface(new FontFamily("Consolas"), FontStyles.Normal, FontWeights.Normal, new FontStretch());

		public Candle[] Candles { get; set; }
		public Indicator[] Indicators { get; set; }

		public WriteableBitmap Draw(WriteableBitmap bitmap, double w, double h, Point mousePosition, int candleWidth, int position, TimeFrame timeFrame, Security security, bool isReal)
		{
			var mouseColor = Colors.Gainsboro;
	
			if (Math.Abs(bitmap.Width - (int)w) > double.Epsilon || Math.Abs(bitmap.Height - (int)h) > double.Epsilon)
			{
				bitmap = bitmap.Resize((int)w, (int)h, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
			}

			if (Candles.Length == 0) return bitmap;

			
			var high = (decimal)0;
			var low = decimal.MaxValue;
			var maxvol = 0;
			var realCandleWidth = Math.Max(candleWidth, 3);

			var positionTo = (int)Math.Min(position + w / candleWidth, Candles.Length - 1);
			if (positionTo <= position) return bitmap;

			for (var j = position; j <= positionTo; j++)
			{
				var candle = Candles[j];
				if (high < candle.High) high = candle.High;
				if (low > candle.Low) low = candle.Low;
				if (maxvol < candle.Volume) maxvol = candle.Volume;
			}
			
			var inth = (int)h;

			var yCoeff = (decimal)((h-30) / (double)(high - low));
			var yFunc = new Func<decimal, int>(arg => inth - 15 - (int)(yCoeff * (arg - low)));

			var isMouse = mousePosition != default(Point);

			var volCoef = (inth / 5.0) / maxvol;
			var middle = realCandleWidth / 2;
			
			bitmap.Lock();
			bitmap.Clear();

			var oldDate = DateTime.MinValue;
			var datePos = true;

			var xValue = 0;
			var xValues = new Dictionary<DateTime, int>();

			var dateInterval = ((int)Math.Max(w / 10, 60)) / candleWidth;
			var dateIntervalCounter = 0;
			var dateGrid = new List<DateTime>();

			for (var j = position; j <= positionTo; j++)
			{
				var candle = Candles[j];
				xValues.Add(candle.Date, xValue);

				if (dateIntervalCounter++ >= dateInterval)
				{
					bool f;
					switch ((TimeFrame.ELength)timeFrame.TimeFrameLength)
					{
						case TimeFrame.ELength.M1:
							f = candle.Date.Hour != oldDate.Hour;
							break;
						case TimeFrame.ELength.M5:
							f = candle.Date.Hour != oldDate.Hour;
							break;
						case TimeFrame.ELength.M15:
							f = candle.Date.Hour != oldDate.Hour;
							break;
						case TimeFrame.ELength.H1:
							f = candle.Date.Date != oldDate.Date;
							break;
						case TimeFrame.ELength.D1:
							f = candle.Date.DayOfWeek == DayOfWeek.Monday;
							break;
						case TimeFrame.ELength.W1:
							f = candle.Date.Month != oldDate.Month;
							break;
						default:
							throw new ArgumentOutOfRangeException();
					}

					if (f)
					{
						dateGrid.Add(candle.Date);
						dateIntervalCounter = 0;
						bitmap.DrawLine(xValue + middle + 1, 0, xValue + middle + 1, inth, Colors.Lavender);
					}
				}

				bitmap.FillRectangle(xValue, inth, xValue + realCandleWidth - 1, inth - (int)(candle.Volume * volCoef), Color.FromArgb(20, 0, 0, 255));

				bitmap.DrawLine(xValue + middle, yFunc(candle.Low), xValue + middle, yFunc(candle.High), Colors.Black);
				var o = yFunc(candle.Open);
				var c = yFunc(candle.Close);
				if (o < c)
				{
					bitmap.FillRectangle(xValue, o, xValue + realCandleWidth, c, Colors.Black);
					if ((c - o) > 2)
					{
						bitmap.FillRectangle(xValue + 1, o + 1, xValue + realCandleWidth - 1, c - 1, Colors.Salmon);
					}
				}
				else
				{
					bitmap.FillRectangle(xValue, c, xValue + realCandleWidth, o, Colors.Black);
					if ((o - c) > 2)
					{
						bitmap.FillRectangle(xValue + 1, c + 1, xValue + realCandleWidth - 1, o - 1, Colors.YellowGreen);
					}
				}
				if (isMouse)
				{
					if ((xValue + candleWidth) >= mousePosition.X)
					{
						bitmap.DrawLine(0, (int)mousePosition.Y, (int)w, (int)mousePosition.Y, mouseColor);
						bitmap.DrawLine((int)mousePosition.X, 0, (int)mousePosition.X, inth, mouseColor);
						DrawText(bitmap, new Point(10, 10), string.Format(
							@"{0:dd-MM-yy HH:mm}
O:{1}
H:{2}
L:{3}
C:{4}
Vol:{5:10}", candle.Date, candle.Open, candle.High, candle.Low, candle.Close, candle.Volume), 12, Brushes.Indigo);
						isMouse = false;
					}
				}

				xValue += candleWidth;
				oldDate = candle.Date;

				if (xValue > w) break;
			}

			if (isMouse)
			{
				bitmap.DrawLine(0, (int)mousePosition.Y, (int)w, (int)mousePosition.Y, mouseColor);
				bitmap.DrawLine((int)mousePosition.X, 0, (int)mousePosition.X, inth, mouseColor);
			}

			var pos = DataManager.Instance.Positions.FirstOrDefault(e => e.SecId == security.Id);

			if (pos != null && pos.Saldo!=0)
			{
				var price = DataManager.Instance.DirectorStorage.GetAveragePrice(security.Code);
				bitmap.DrawLine(0, yFunc(price), (int)w, yFunc(price), Colors.Green);
			}

			var orders = OrdersRegistrator.Instance.GetOrders(security, isReal);
			var orderX = orders.ToDictionary(o => o, o => -1);
			var orderY = orders.ToDictionary(o => o, o => -1);

			var orderIndex = 0;
			while (orderIndex<orders.Length && orders[orderIndex].Time<Candles[position].Date)
			{
				orderIndex++;
			}

			xValue = 0;

			foreach (var indicator in Indicators.OfType<SeriesChartIndicator>())
			{
				var x = 0;
				var index = 0;
				int[] lineData = null;
				for (var j = position; j <= positionTo; j++)
				{
					var d = indicator.Data[j];
					if (d.HasValue)
					{
						if (lineData == null)
						{
							lineData = new int[(positionTo - j + 1) * 2];
						}
						lineData[index] = x;
						lineData[index + 1] = yFunc(d.Value);
						index += 2;
					}

					if (orderIndex < orders.Length && orders[orderIndex].Time <= Candles[j].Date)
					{
						var order = orders[orderIndex];

						orderX[order] = x;
						if (isReal)
						{
							orderY[order] = yFunc(DataManager.Instance.DirectorStorage.GetAveragePriceByOrder(order.OrderNo) ?? Candles[j].Close);
						}
						else
						{
							orderY[order] = yFunc(Candles[j].Close);
						}

						bitmap.FillEllipseCentered(x, orderY[order], 5, 5, order.IsBuy ? Colors.Green : Colors.Red);

						


						if (!order.IsBuy && orderIndex > 0 && orderX[orders[orderIndex - 1]] > 0)
						{
							var prevOrder = orders[orderIndex - 1];
							bitmap.DrawLine(orderX[prevOrder], orderY[prevOrder], orderX[order], orderY[order], orderY[order] < orderY[prevOrder] ? Colors.Green : Colors.Red);
							bitmap.DrawLine(orderX[prevOrder], orderY[prevOrder] + 1, orderX[order], orderY[order] + 1, orderY[order] < orderY[prevOrder] ? Colors.Green : Colors.Red);
							bitmap.DrawLine(orderX[prevOrder], orderY[prevOrder] - 1, orderX[order], orderY[order] - 1, orderY[order] < orderY[prevOrder] ? Colors.Green : Colors.Red);
						}
						orderIndex++;
					}

					x += candleWidth;
				}
				if (lineData != null)
				{
					bitmap.DrawPolyline(lineData, indicator.Color);
				}
			}

			foreach (var indicator in Indicators.OfType<SignChartIndicator>())
			{
				var solidColorBrush = new SolidColorBrush(indicator.Color);
				
				foreach (var sign in indicator.Signs)
				{
					int x;
					if (xValues.TryGetValue(sign.DateTime, out x))
					{
						DrawText(bitmap, new Point(x + middle, yFunc(sign.Value)), sign.Text, 10, solidColorBrush, TextAlignment.Center);
					}
				}
			}

			foreach (var dateTime in dateGrid)
			{
				var x = xValues[dateTime];
				switch ((TimeFrame.ELength)timeFrame.TimeFrameLength)
				{
					case TimeFrame.ELength.M1:
					case TimeFrame.ELength.M5:
					case TimeFrame.ELength.M15:
						DrawText(bitmap, new Point(x, inth - 11), dateTime.ToString("HH:mm dd MMM yy"), 10, Brushes.Black, TextAlignment.Center);
						break;
					case TimeFrame.ELength.H1:
					case TimeFrame.ELength.D1:
					case TimeFrame.ELength.W1:
						DrawText(bitmap, new Point(x, inth - 11), dateTime.ToString("dd MMM yy"), 10, Brushes.Black, TextAlignment.Center);
						break;
					default:
						throw new ArgumentOutOfRangeException();
				}
			}

			bitmap.Unlock();

			return bitmap;
		}

		public static int DrawText(WriteableBitmap wBmp, Point at, string text, double fontSize, Brush textColor, TextAlignment alignment = TextAlignment.Left)
		{
			var ftext = new FormattedText(text, new CultureInfo("en-us"), FlowDirection.LeftToRight, _typeface, fontSize, textColor);

			var drawingVisual = new DrawingVisual();
			var drawingContext = drawingVisual.RenderOpen();
			drawingContext.DrawText(ftext, new Point(0, 0));
			drawingContext.Close();

			var bmp = new RenderTargetBitmap((int)ftext.Width, (int)ftext.Height, 96, 96, PixelFormats.Pbgra32);
			bmp.Render(drawingVisual);

			switch (alignment)
			{
				case TextAlignment.Left:
					wBmp.Blit(at, new WriteableBitmap(bmp), new Rect(0, 0, bmp.Width, bmp.Height), Colors.White, WriteableBitmapExtensions.BlendMode.None);
					return (int)(at.X + bmp.Width);
				case TextAlignment.Center:
					wBmp.Blit(new Point(at.X - ftext.Width / 2, at.Y), new WriteableBitmap(bmp), new Rect(0, 0, bmp.Width, bmp.Height), Colors.White, WriteableBitmapExtensions.BlendMode.None);
					return (int)(at.X + ftext.Width / 2);
				default:
					throw new ArgumentOutOfRangeException("alignment");
			}
		}
	}
}
